package jcogo;

import java.text.DecimalFormat;

/**
* Angle is a class to hold an angle in various formats. It can deal
* with angles in degrees, minutes and seconds, or decimal degrees. It
* also has functions to add angles and other basic angular arithmetic.
* <p>
* This file is part of jcogo.
* <p>
* jcogo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* jcogo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with jcogo. If not, see <a href="http://www.gnu.org/licenses/">
* http://www.gnu.org/licenses/</a>
*
* @author Nathaniel Schwartz
*
* Copyright 2010 Nathaniel Schwartz.
*/

public class Angle implements Comparable {
  /**
   * The degrees for this Angle.
   */
  private int degrees;

  /**
   * The minutes for this Angle.
   */
  private int minutes;

  /**
   * The seconds for this Angle.
   */
  private int seconds;

  /**
   * Constructs an Angle given a String that contains the degrees, 
   *    minutes and seconds separated by a space - each should be
   *    an integer.
   * @param a a string containing the degrees, minutes and seconds
   *    for this Angle.
   */
  public Angle(String a) {
    try {
      Scanner s = new Scanner(a);
      // Take only degrees less than 360.
      degrees = s.nextInt() % 360;
      minutes = s.nextInt();
      seconds = s.nextInt();
      // More than 60 seconds or minutes is an error.
      if (seconds > 60 || minutes > 60)
        throw new InputMismatchException();
    }
    catch (NoSuchElementException e) {
      throw new InputMismatchException(
        "Incomplete or invalid angle data. ");
    }
  }

  /**
   * Constructs an Angle given explicit degrees, minutes and seconds.
   * @param degrees the number of degrees in the Angle.
   * @param minutes the number of minutes in the Angle.
   * @param seconds the number of seconds in the Angle.
   */
  public Angle(int degrees, int minutes, int seconds) {
    // Take only degrees less than 360.
    this.degrees = degrees % 360;
    this.minutes = minutes;
    this.seconds = seconds;
    // More than 60 seconds or minutes is an error.
    if (seconds > 60 || minutes > 60)
      throw new InputMismatchException("The value for minutes"
        + " and seconds must both be 60 or fewer. ");
  }

  /**
   * Constructs an Angle given decimal degrees.
   * @param a the double value for the degrees.
   */
  public Angle(double a) {
    angleToHMS(a);
  }

  /**
   * Returns the integer value for the degrees of this Angle.
   * @return the integer value for the degrees of this Angle.
   */
  public int getDegrees() {
    return degrees;
  }

  /**
   * Returns the integer value for the minutes of this Angle.
   * @return the integer value for the minutes of this Angle.
   */
  public int getMinutes() {
    return minutes;
  }

  /**
   * Returns the integer value for the seconds of this Angle.
   * @return the integer value for the seconds of this Angle.
   */
  public int getSeconds() {
    return seconds;
  }

	/**
	 * Returns a String representation of this Angle.
	 * @return the String representation of this Angle. 
	 */
  public String toString() {
    return "" + degrees + "\u00B0" + minutes + "'" + seconds + "\"";
  }

  /**
   * Converts a double value to degrees, minutes and seconds; then it
   * stores those values.
   */
  private void angleToHMS(double angle) {
    degrees = (int) angle;
    double min = (angle - degrees) * 60;
    minutes = (int) min;
    double sec = (min - minutes) * 60;
    seconds = (int) Math.round(sec);
  }

  /**
   * Compares this Angle to another Angle.
   * @param o the (Angle) Object with which this is to be compared.
   * @return -1 if this Angle is smaller than o, 0 if they are equal
   *    and 1 if this is bigger than o.
   */
  public int compareTo(Object o) {
    // This is INCOMPLETE!!!!
    Angle a = (Angle) o;
    if (this.degrees < a.getDegrees()) return -1;
    else if (this.degrees > a.getDegrees()) return 1;
    // Need to consider if degrees are equal, etc.
    return 0;
  }

  /**
   * Converts this Angle to decimal degrees.
   * @return the decimal degrees of this Angle.
   */
  public double toDouble() {
    double angle = 0;
    angle += (double) this.seconds / (double) 3600.0;
    angle += (double) this.minutes / (double) 60.0;
    angle += (double) this.degrees;
    return angle;
  }

  /**
   * Converts this Angle to radians.
   * @return the radians of this Angle.
   */
  public double toRadians() {
    return this.toDouble()*(Math.PI/180.0);
  }

  /**
   * Adds two Angles together and returns their sum.
   * @param a the first Angle.
   * @param b the second Angle.
   * @return the sum of Angles a and b.
   */
  public static Angle add(Angle a, Angle b) {
    int degrees = 0;
    int minutes = 0;
    int seconds = 0;
    int totalSeconds = a.getSeconds() + b.getSeconds();
    seconds = totalSeconds % 60;
    int totalMinutes = a.getMinutes() 
      + b.getMinutes() + (totalSeconds / 60);
    minutes = totalMinutes % 60;
    int totalDegrees = a.getDegrees() 
      + b.getDegrees() + (totalMinutes / 60);
    degrees = totalDegrees % 360;
    return new Angle(degrees, minutes, seconds);
  }
}
